#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#define BUF_LEN 100


#include "mychar.h"

//主设备号
int major = 11;
//次设备号
int minor = 0;
//申请数量
int mychar_num = 1;

//自定义字符设备
struct mychar_cdev {

	//定义字符设备
	struct cdev mydev;
	unsigned int led2gpio;
	unsigned int led3gpio;
	unsigned int led4gpio;
	unsigned int led5gpio;
};

struct mychar_cdev * pgdev = NULL;

int mychar_open(struct inode *pnode, struct file *pfile){

	//将自定义字符设备的地址赋值给 private_data 变量
	//这样后续的操作函数都不需要使用全局变量
	pfile->private_data = (void *)container_of(pnode->i_cdev, struct mychar_cdev, mydev);
	printk("mychar_open is called \n");

	return 0;
}

int mychar_close(struct inode *pnode, struct file *pfile){
	printk("mychar_close is called \n");
	return 0;
}


long mychar_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg){

	//拿到自定义字符设备的地址
	struct mychar_cdev *pmychardev = (struct mychar_cdev *)pfile->private_data;

	//将arg先转换成用户空间指针形式
	int __user *uarg = (int * )arg;

	switch(cmd){
		//对当前状态取反
		case LED_CONTROL:
			if(*uarg == 2){
				
				if(gpio_get_value(pmychardev->led2gpio) == 0){
					gpio_set_value(pmychardev->led2gpio, 1);
				}else{
					gpio_set_value(pmychardev->led2gpio, 0);	
				}

			}else if(*uarg == 3){

				if(gpio_get_value(pmychardev->led3gpio) == 0){
					gpio_set_value(pmychardev->led3gpio, 1);
				}else{
					gpio_set_value(pmychardev->led3gpio, 0);	
				}

			}else if(*uarg == 4 ){
				
				if(gpio_get_value(pmychardev->led4gpio) == 0){
					gpio_set_value(pmychardev->led4gpio, 1);
				}else{
					gpio_set_value(pmychardev->led4gpio, 0);	
				}

			}else if(*uarg == 5){

				if(gpio_get_value(pmychardev->led5gpio) == 0){
					gpio_set_value(pmychardev->led5gpio, 1);
				}else{
					gpio_set_value(pmychardev->led5gpio, 0);	
				}

			}
	default:
		return -1;
	}
	return 0;
}


void led_dark(struct mychar_cdev *pmychardev, struct device_node * pnode){

	gpio_direction_output(pmychardev->led2gpio, 0);
	gpio_direction_output(pmychardev->led3gpio, 0);
	gpio_direction_output(pmychardev->led4gpio, 0);
	gpio_direction_output(pmychardev->led5gpio, 0);
}


void led_init(struct mychar_cdev *gdev, struct device_node * pnode){
	gdev->led2gpio = of_get_named_gpio(pnode, "led2-gpio", 0);
	gpio_request(gdev->led2gpio, "led2");

	gdev->led3gpio = of_get_named_gpio(pnode, "led3-gpio", 0);
	gpio_request(gdev->led3gpio, "led3");

	gdev->led4gpio = of_get_named_gpio(pnode, "led4-gpio", 0);
	gpio_request(gdev->led4gpio, "led4");

	gdev->led5gpio = of_get_named_gpio(pnode, "led5-gpio", 0);
	gpio_request(gdev->led5gpio, "led5");

}


void led_release(struct mychar_cdev *gdev){

	gpio_free(gdev->led2gpio);
	gpio_free(gdev->led3gpio);
	gpio_free(gdev->led4gpio);
	gpio_free(gdev->led5gpio);
}

//定义操作函数
struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.unlocked_ioctl = mychar_ioctl,
};

int __init mychar_init(void)
{
	int ret = 0;
	struct device_node * pnode = NULL;

	//在设备树中找到对应的节点
	pnode = of_find_node_by_path("/fs4412-leds");
	if(pnode == NULL){
		printk("cannot find node \n");
		return -1;
	}

	//设备号
	dev_t devno = MKDEV(major, minor);
	//从当前设备号开始验证，mychar_num数量的设备号。若系统没有占用，则申请占用mychar_num数量的设备号
	//"mychar": 该设备号在/proc/devices文件中的名称。意义：确保设备号申请成功，及方便后续据此设备号、名称创建设备文件
	ret = register_chrdev_region(devno, mychar_num, "mychar");

	if(ret){
		//系统动态分配
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		//此时设备号是由系统分配的
		major = MAJOR(devno);
	}

	pgdev =(struct mychar_cdev *) kmalloc(sizeof(struct mychar_cdev), GFP_KERNEL);
	if(pgdev == NULL){
		printk("kmalloc free failed \n");
		//释放设备号
		unregister_chrdev_region(devno, mychar_num);
		return -1;
	}
	memset(pgdev, 0, sizeof(struct mychar_cdev));

	//初始化字符设备,指定所属内核模块，指定操作函数集
	pgdev->mydev.owner = THIS_MODULE;
	cdev_init(&pgdev->mydev, &myops);

	//将cdev对象添加到内核的字符设备链表中
	cdev_add(&pgdev->mydev, devno, mychar_num);

	//初始化GPIO编号、向内核申请GPIO
	led_init(pgdev, pnode);

	//设置GPIO输出模式，并设初始值为0
	led_dark(pgdev, pnode);

	return 0;
}

void __exit mychar_exit(void)
{

	dev_t devno = MKDEV(major, minor);

	led_release(pgdev);
	
	//从内核的字符设备链表中移除该设备
	cdev_del(&pgdev->mydev);

	//释放kmalloc的空间
	kfree(pgdev);
	pgdev = NULL;

	//释放设备号
	unregister_chrdev_region(devno, mychar_num);

}

MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
